日志功能扩展:收集与筛选日志需求
NestJS 项目上线后,日志管理是不可忽视的运维需求。本节分析当前文件日志方案的局限性,并引出日志集中化存储的解决方案。从本地文件日志到 HTTP Transport 再到 MongoDB Transport,逐步解决日志收集、筛选和存储的问题。
现有日志方案回顾
项目中已有一个 WinstonModule,通过 create-rotate-transport.ts 将日志以文件形式存储在 logs/ 目录下,并按日期滚动。
// common/winston/create-rotate-transport.ts
// 将日志按日期滚动存储到 logs/ 目录
typescript
文件日志方案的痛点
| 问题 | 说明 |
|---|---|
| 磁盘 IO 占用 | 写日志消耗系统磁盘 IO,可能影响业务性能 |
| 不方便浏览和下载 | 日志以文件形式存储在服务器上,查看需要登录服务器 |
| 不方便筛选 | 文本文件中查找特定错误日志效率低 |
| 占用系统空间 | 日志文件随时间增长,需要定期清理 |
| 日志分散 | 多节点部署时日志分散在不同服务器上,汇总困难 |
日志方案的演进路线
从简单到复杂,日志管理方案可以分为以下几个层级:
Level 1: 本地文件日志(当前方案)
↓ 解决磁盘 IO 问题
Level 2: HTTP Transport(发送到远程服务器)
↓ 解决多服务日志筛选问题
Level 3: 数据库存储(MongoDB Transport)
↓ 解决分布式日志汇总问题
Level 4: 分布式日志系统(ELK Stack / Grafana Loki / Sentry)
text
Level 2: HTTP Transport
Winston 提供 Http Transport,通过 HTTP 协议将日志发送到远程静态资源服务器:
const winston = require('winston')
const logger = winston.createLogger({
transports: [
new winston.transports.Http({
host: 'logs.example.com',
port: 443,
ssl: true,
path: '/api/logs',
auth: {
bearer: 'your-api-token', // 简单鉴权,防止未授权写入
},
headers: {
'X-Custom-Header': 'value',
},
// 批量发送配置
batch: true,
batchInterval: 5000, // 每 5 秒发送一次
batchCount: 10, // 或累积 10 条后发送
}),
],
})
javascript
HTTP Transport 优缺点
| 优点 | 缺点 |
|---|---|
| 解决本地磁盘 IO 问题 | 占用网络带宽 |
| 支持简单鉴权 | 发送频率不能太高 |
| 类似自建对象云存储 | 多服务日志筛选仍然困难 |
| 适合单服务器简单业务 | 无法快速查询错误日志 |
适用场景:单服务器单 NestJS 实例,业务不复杂的小程序或简单业务系统。
Level 3: MongoDB Transport
Winston 官方维护的 winston-mongodb Transport,将日志直接写入 MongoDB 数据库,兼顾存储和查询筛选能力。
安装
pnpm add winston-mongodb
bash
配置示例
const winston = require('winston')
require('winston-mongodb')
const logger = winston.createLogger({
transports: [
new winston.transports.MongoDB({
db: 'mongodb://localhost:27017/logs', // MongoDB 连接字符串
collection: 'app-logs', // 存储日志的集合名称
level: 'info', // 最低日志级别
// Capped Collection 配置(自动清理旧日志)
capped: true,
cappedSize: 10000000, // 集合大小上限(10MB)
cappedMax: 10000, // 最大文档数
// 或者使用 TTL 索引自动过期
// expireAfterSeconds: 86400 * 30, // 30 天后自动删除
// 其他选项
storeHost: true, // 存储主机名
tryReconnect: true, // 连接失败时自动重连
options: {
poolSize: 2,
autoReconnect: true,
},
}),
],
})
javascript
MongoDB Transport 核心配置
| 参数 | 类型 | 说明 |
|---|---|---|
db | string | MongoDB 连接 URI |
collection | string | 日志集合名称,默认 log |
level | string | 最低日志级别,默认 info |
capped | boolean | 是否使用固定大小集合,默认 false |
cappedSize | number | 固定集合大小(字节),默认 10000000 |
cappedMax | number | 固定集合最大文档数 |
expireAfterSeconds | number | 文档过期时间(秒),与 capped 互斥 |
storeHost | boolean | 是否存储主机名 |
tryReconnect | boolean | 连接失败是否自动重连 |
Capped Collection vs TTL 索引
| 特性 | Capped Collection | TTL 索引 |
|---|---|---|
| 自动清理 | 按大小滚动,超出大小覆盖最旧数据 | 按时间过期,到期自动删除 |
| 查询性能 | 插入性能极高 | 普通索引查询 |
| 适用场景 | 固定存储空间,持续写入 | 需要精确控制日志保留时间 |
为什么选择数据库存储
- 不占用业务系统 IO:日志写入独立的数据库服务器
- 支持筛选和查询:使用 MongoDB 的查询语法快速定位错误日志
- 多服务集中存储:多个服务可以同时写入同一个数据库
- 自动维护:通过 Capped Collection 或 TTL 实现日志自动清理
在 NestJS 中集成 MongoDB Transport
修改现有的 Winston 模块配置,增加 MongoDB Transport 作为可选方案:
// common/winston/winston.module.ts
import { Module } from '@nestjs/common'
import { WinstonModule } from 'nest-winston'
@Module({
imports: [
WinstonModule.forRootAsync({
useFactory: (configService: ConfigService) => {
const transports = [
// 原有的文件日志(开发环境)
createRotateTransport(),
]
// 根据配置添加 MongoDB Transport
if (configService.get('LOG_MONGODB_ENABLED') === 'true') {
transports.push(
new transports.MongoDB({
db: configService.get('LOG_MONGODB_URI'),
collection: configService.get('LOG_MONGODB_COLLECTION', 'app-logs'),
level: configService.get('LOG_LEVEL', 'info'),
capped: true,
cappedSize: 10000000,
})
)
}
return { transports }
},
inject: [ConfigService],
}),
],
})
export class LoggerModule {}
typescript
对应的 .env 配置:
# 日志 MongoDB 配置
LOG_MONGODB_ENABLED=true
LOG_MONGODB_URI=mongodb://localhost:27017/logs
LOG_MONGODB_COLLECTION=app-logs
LOG_LEVEL=info
env
日志类型分类
通常需要记录的日志类型:
| 日志类型 | 级别 | 说明 |
|---|---|---|
| 运行日志 | info | 服务启动、路由注册等正常信息 |
| 错误日志 | error | 未捕获异常、接口错误等 |
| 警告日志 | warn | 非致命性问题,如参数不推荐 |
| 主动暴露日志 | debug | 开发调试时的主动日志输出 |
| 系统状态日志 | info | 内存使用、连接数等系统指标 |
Level 4: 分布式日志系统(前瞻)
当服务端项目变得复杂,需要多节点部署和负载均衡时,分布式日志系统是最终方案:
| 工具 | 定位 | 特点 |
|---|---|---|
| ELK Stack (Elasticsearch + Logstash + Kibana) | 日志收集 + 搜索 + 可视化 | 功能全面,资源消耗较大 |
| Grafana Loki + Fluentd | 日志聚合 + 查询 | 轻量级,与 Grafana 深度集成 |
| Sentry | 错误收集 + 监控 | 专注错误追踪,支持 SourceMap 还原 |
分布式日志系统将在后续章节扩展,当前阶段重点关注 MongoDB Transport 的方案。
小结
本节分析了日志管理的需求演进,核心要点:
- 文件日志(当前方案):适合开发环境,但存在磁盘 IO、不便筛选等问题
- HTTP Transport:解决磁盘 IO 问题,但不适合多服务场景
- MongoDB Transport(推荐方案):兼顾存储和查询,支持多服务集中管理
- Capped Collection:自动清理旧日志,避免无限增长
- 分布式日志系统:多节点部署场景的终极方案
下一节将实装 MongoDB Transport,完成日志数据库存储的完整实现。
↑